/*
    SEL - Simple DirectMedia Layer Extension Library
    Copyright (C) 2002 Matej Knopp <knopp@users.sf.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "font.h"
#include "../base/surface.h"
#include "../base/debug.h"
#include "../base/misc.h"

namespace SEL
{

FontCharacter::FontCharacter (Surface *surface)
{
	m_converted_surface = NULL;
	m_original_surface = surface;
	m_width = 0;
	m_character = 0;
}
	
FontCharacter::~FontCharacter ()
{
	if (m_converted_surface)
		m_converted_surface->unref ();
		
	if (m_original_surface)
		m_original_surface->unref ();
}

void 
FontCharacter::setOriginalSurface (Surface *surface)
{
	if (m_original_surface)
		m_original_surface->unref ();
	m_original_surface = surface;
}

void 
FontCharacter::setConvertedSurface (Surface *surface)
{
	if (m_converted_surface)
		m_converted_surface->unref ();
	m_converted_surface = surface;
}

void 
FontCharacter::convertSurface (const SDL::PixelFormat &dest_fmt,
			       Uint32 dest_flags)
{
	if (m_converted_surface)
		m_converted_surface->unref ();
	if (m_original_surface->flags () & SDL::SRCALPHA)
	{
		m_converted_surface = 
			Surface::convertSurfaceAlpha (m_original_surface,
						      dest_fmt);

		m_converted_surface->setAlphaFlags (SDL::SRCALPHA |
						    SDL::RLEACCEL);
	}
	else
	{
		m_converted_surface =
			Surface::convertSurface (m_original_surface,
						 dest_fmt, 
						 m_original_surface->flags ());

		m_converted_surface->setColorKeyFlags (SDL::SRCCOLORKEY |
						       SDL::RLEACCEL);
	}
}

void
FontCharacter::setWidth (Uint16 width)
{
	m_width = width;
}

void
FontCharacter::resetWidth ()
{
	if (m_converted_surface)
		m_width = m_converted_surface->w();
}

Font::Font (Surface *src, const std::string &map, Uint16 space_width, 
	    Uint16 min_gap_width, Uint8 alpha_treshold)
	: Object ()
{
	m_target_surface = NULL;
	m_space_width = space_width;
	m_v_spacing = 0;
	m_h_spacing = 0;
	m_height = 0;
	importFont (src, map, min_gap_width, alpha_treshold);
}

Font::~Font ()
{
	std::vector<FontCharacter*>::iterator it = m_characters.begin ();
	while (it != m_characters.end ())
	{
		FontCharacter *character = (*it);
		del (character);
		it++;
	}
	m_characters.clear ();
	debugI ("Font::~Font ()");
}

bool
Font::pixelTransparent (Surface *surface, Uint32 pixeladdr,
			Uint8 alpha_treshold)
{
	Uint32 *_pixel = (Uint32 *) ((Uint8 *) surface->pixels () + pixeladdr);
	Uint32 __pixel = *_pixel;
	Uint32 *pixel = &__pixel;

	if ((surface->flags () & SDL::SRCALPHA) == SDL_SRCALPHA) 
	{
		// suface with an alpha chanel - use alpha_treshold
		Uint8 alpha = (*pixel & surface->format ()->Amask) 
				>> surface->format ()->Ashift;
		alpha = (alpha << surface->format ()->Aloss) + 
			(alpha >> (8 - surface->format ()->Aloss));
		return alpha <=	alpha_treshold;
	} 
	else 
	{
		if (surface->format ()->BytesPerPixel > 1)
			return ((*pixel & (surface->format ()->Rmask |
					   surface->format ()->Gmask |
					   surface->format ()->Bmask)) ==
				surface->format ()->colorkey);
		else
			return (*pixel & 0xFF) == surface->format ()->colorkey;
	}
}

bool
Font::vLineTransparent (Surface *surface, Uint16 line, 
			Uint8 alpha_treshold)
{
	Uint16 i;
	Uint32 pixeladdr = line * surface->format ()->BytesPerPixel;
	const Uint16 pitch = surface->pitch ();

	for (i = 0; i < surface->h (); i++) 
	{
		if (!pixelTransparent (surface, pixeladdr, alpha_treshold))
			return false;
		pixeladdr += pitch;
	}

	return true;
}

bool
Font::hLineTransparent (Surface *surface, Uint16 line, 
			Uint8 alpha_treshold)
{
	Uint16 i;
	Uint32 pixeladdr = line * surface->pitch ();
	const Uint16 bpp = surface->format ()->BytesPerPixel;

	for (i = 0; i < surface->w (); i++) 
	{
		if (!pixelTransparent (surface, pixeladdr, alpha_treshold))
			return false;
		pixeladdr += bpp;
	}

	return true;
}

void 
Font::importFont (Surface *src, const std::string &map,
		  Uint16 min_gap_width, Uint16 alpha_treshold)
{
	int delta_y, height;
	
	// ignore blank lines at the top
	src->lock ();
	for (delta_y = 0; delta_y < src->h (); delta_y ++)
	{
		if (!hLineTransparent (src, delta_y, alpha_treshold))
			break;
	}
	src->unlock ();
	debugI ("FONT: delta_y = " << delta_y);
	
	// ignore blank lines at the bottom
	src->lock ();
	for (height = src->h (); height > 0; height--)
	{
		if (!hLineTransparent (src, height-1, alpha_treshold))
			break;
	}
	src->unlock ();
	height -= delta_y;
	debugI ("FONT: height = " << height);
	m_height = height;

	int pos = 0; // the position in the surface

	for (unsigned int char_nr = 0; char_nr < map.length (); ++ char_nr)
	{
		// skip blank lines
		src->lock ();
		while ( (pos < src->w ()) &&
		       (vLineTransparent (src, pos, alpha_treshold) == true))
			pos++;
		src->unlock ();

		Uint16 transp_count = 0; // the number of transparent lines
		Uint16 width = 0; // the width of the character
		
		// compute the width (ignore less empty lines
		// than min_gap_width)
		while (1)
		{
			if (pos + width + transp_count >= src->w ())
			{
				// hmm... better stop
				goto FINISH;
			}

			src->lock ();
			if (vLineTransparent (src, pos + width + transp_count, 
					      alpha_treshold))
			{
				transp_count ++;
			}
			else
			{
				width += transp_count + 1 ;
				transp_count = 0;
			}
			src->unlock ();

			if (transp_count == min_gap_width)
			{
				break;
			}
		}

		// create the character surface
		Surface *surface = new Surface (src->flags (), width, height,
						*src->format ());
						
		if (src->format ()->palette)
		{
			// copy the palette
			surface->setColors (src->format ()->palette->colors,
					    0,
					    src->format ()->palette->ncolors);
		}
		
		// Copy the surface data
		Uint32 old_flags = src->flags ();
		Uint32 colorkey = src->format ()->colorkey;
		Uint8  alpha = src->format ()->alpha;
		
		src->setColorKeyFlags (0);
		src->setAlphaFlags (0);
		
		Rect rect(pos, delta_y, width, height);
	
		surface->blit (src, &rect);
		
		// correct the flags
		if ((old_flags & SDL::SRCCOLORKEY) == SDL::SRCCOLORKEY)
 		{
			Uint32 flags = SDL::SRCCOLORKEY;
			flags |= old_flags & SDL::RLEACCEL;
			src->setColorKey (colorkey, flags);
			surface->setColorKey (src->format ()->colorkey,
					      SDL::SRCCOLORKEY | SDL::RLEACCEL);			
		}
		else if ((old_flags & SDL::SRCALPHA) == SDL::SRCALPHA)
		{
			Uint32 flags = SDL::SRCALPHA;
			flags |= old_flags & SDL::RLEACCEL;
			src->setAlpha (alpha, flags);
			surface->setAlpha (src->format ()->alpha,
					   SDL::SRCALPHA | SDL::RLEACCEL);
		}
		
		// create the character
		FontCharacter *character = new FontCharacter (surface);
		character->setCharacter ((Uint8)map [char_nr]);
		character->setWidth (width);		
		m_characters.push_back (character);
		
		pos += width;
	}	

FINISH:
	// clear the character map
	memset (m_character_map, 0, sizeof (m_character_map));

	// fill the character map
	debugI ("FONT: character count = " << m_characters.size ());
	for (unsigned int character = 0; character < m_characters.size (); ++ character)
	{
		m_character_map [m_characters [character]->character () & 0xFF] = character;
	}
}

void 
Font::setTargetSurface (Surface *surface)
{
	if (surface == NULL)
		releaseTargetSurface ();
	m_target_surface = surface;
	m_target_surface->ref ();
	for (unsigned int i = 0; i < m_characters.size (); i++)
	{
		m_characters[i]->convertSurface (*surface->format (),
						 surface->flags ());
	}	
}

void
Font::releaseTargetSurface ()
{
	if (m_target_surface)
		m_target_surface->unref ();
	m_target_surface = NULL;
	for (unsigned int i = 0; i < m_characters.size (); i++)
	{
		Surface *surface = m_characters[i]->convertedSurface ();
		if (surface)
		{
			surface->unref ();
			m_characters[i]->setConvertedSurface (NULL);
		}	
	}	
}

void 
Font::setSpaceWidth (Uint16 width)
{
	m_space_width = width;
}

void
Font::setHSpacing (Sint16 spacing)
{
	m_h_spacing = spacing;
}

void
Font::setVSpacing (Sint16 spacing)
{
	m_v_spacing = spacing;
}

Sint16
Font::computeWidth (const std::string &text) const
{
	Sint16 max_w = 0;
	Sint16 w = 0;
	
	for (unsigned int i = 0; i < text.length (); i++)
	{
		if (text[i] == ' ')
		{
			w += m_space_width;
			continue;
		}
		if (text[i] == '\n')
		{
			if (w > max_w)
				max_w = w;
			w = 0;
			continue;
		}
		w += m_characters[m_character_map[text[i]]]->width () + m_h_spacing;
	}
	return (abs(max_w) > abs(w) ? max_w : w);
}

Sint16
Font::computeHeight (const std::string &text) const
{
	Sint16 height = m_height;
	for (unsigned int i = 0; i < text.length (); i++)
	{
		if (text[i] == '\n')
			height += m_height + m_v_spacing;
	}
	return height;
}

void 
Font::print (Sint16 x, Sint16 y, const std::string &text)
{
	if (m_target_surface == NULL)
		return;
		
	Sint16 dx = x;
	
	for (unsigned int i = 0; i < text.length (); i++)
	{
		if (text[i] == ' ')
		{
			dx += m_space_width;
			continue;
		}
		if (text[i] == '\n')
		{
			dx = x;
			y += m_height + m_v_spacing;
			continue;
		}

		FontCharacter *character = m_characters [m_character_map [text[i]]];
		m_target_surface->blit ((character->convertedSurface ()), dx, y);
		dx += character->width () + m_h_spacing;
	}
}

} // namespace SEL
